-- 修改require加载路径顺序
package.path = './?.lua;./?/init.lua;./lua/5.3/?.lua;./lua/5.3/?/init.lua'

local load = load
local pack = table.pack
local traceback = debug.traceback
local xpcall = xpcall

local js = require "js"
local global = js.global
local console = global.console
local document = global.document
local location = global.location
local Vue = global.Vue

local vue_version = string.sub(Vue.version, 1, 1)
local vue_app = nil
if(vue_version == '3') then vue_app = Vue.createApp() end

local request_version = ''

-- 全局组件关系表
local components_relation = {}
-- 全局组件回调表
local components_callback = {}
-- 全局加载中组件表
local requesting_component = {}

-- 全局已加载组件表
registered_component = {}

-- 全局模版临时表
component_template = {}

-- 异步请求
local function asyncXhrGet(url, callback)

    -- 创建对象
    local xhr = js.new(global.XMLHttpRequest)

    xhr.onload = function(self)
        -- 判断请求结果
        if self.status >= 200 and self.status <= 299 then
            -- 返回数据
            callback(self.responseText)
            return true
        end
        error('加载失败:'..url)
    end

    -- 判断是否指定了请求版本
    if request_version ~= '' then

        local index = string.find(url, [[?]])

        -- 链接里没带问号
        if index == nil then
            url = url .. [[?v=]] .. request_version
        end
    end

    -- 发起异步请求
    xhr:open("GET", url, true)
    xhr:send()

    return nil
end

local function parseBaseUrl()

    -- 获取base节点
    local base_el = document:querySelectorAll('base')

    if(base_el[0] ~= nil and base_el[0] ~= js.null) then

        -- 获取base节点的href属性
        local url = base_el[0]:getAttribute('href')

        -- 返回base url
        if(url ~= nil and url ~= js.null and url ~= '') then
            return url
        end
    end

    -- 将location.pathname倒序
    local str = string.reverse(location.pathname)

    -- 获取倒序后/符号的位置
    local a, i = string.find(str, '/')

    -- 获取/符号在原始字符串中的位置
    local j = string.len(str) - i + 1

    -- 截取原始字符串到/符号的部分
    local pathname = string.sub(location.pathname, 1, j)

    return location.origin .. pathname
end

local function parseAbsoluteUrl(url)

    local base_url = parseBaseUrl()

    -- 不分析../和./，要求所有组件的地址必须相对于根目录

    -- 直接返回
    return base_url .. string.gsub(url, '%.%/', '')
end

local function parseUrlToName(url)

    -- 必须将地址中的-符号，替换成%-
    local base_url = string.gsub(parseBaseUrl(), '%-', '%%-')

    local base_s, base_e = string.find(url, base_url)

    local vue_s, vue_e = string.find(url, '%.vue')

    -- 获取主要部分，即去掉base url 和 .vue
    local name = string.sub(url, base_e, vue_s - 1)

    -- 将/替换成_
    return string.gsub(name, '/', '_')
end

local function appendStyle(name, style)

    local el = document:createElement('style')

    el.id = name
    el.type = 'text/css'
    el.innerHTML = style

    local head = document:querySelector('head')
    head:appendChild(el)
end

local function parseVueSFC(content)

    local template = ''
    local script = ''
    local style = ''

    -- 创建div节点
    local el = document:createElement('div')
    -- 将vue组件内容添加到节点上
    el.innerHTML = content
    for i = 0, el.children.length - 1 do
        local child = el.children[i]
        if(child.tagName == 'TEMPLATE') then
            template = child.innerHTML
        end
        -- if(child.tagName == 'SCRIPT') then
        --     script = child.innerHTML
        -- end
        if(child.tagName == 'STYLE') then
            style = child.innerHTML
        end
    end
    script = string.match(content,'<script.->(.+)</script>')

    return template, script, style
end

local function registerVueSFC(url, template, script, style)

    local name = parseUrlToName(url)

    if(style ~= nil and style ~= '') then
        appendStyle(name, style)
    end

    -- 将模版保存到全局变量中使用
    component_template[name] = template

    -- 将模版补充到对象代码里面
    script = string.gsub(script, 'loader%.export%(%{', "return loader.obj({\ntemplate=component_template['"..name.."'];\n", 1)

    -- 根据代码字符串加载生成函数
    local fn, err = load(script, name)
    local setting = nil

    if fn then
        -- 执行代码
        local results = pack(xpcall(fn,traceback))
        if results[1] then
            -- 代码执行成功时，获取vue组件配置对象
            setting = results[2]
        end
    else
        -- 组件文件的lua代码有错误时，显示错误文件及信息
        console:error('fail to register: ' .. url .. '\n\n' .. err)
        return false;
    end

    if(vue_version == '3') then
        -- Vue3版本
        -- 注册vue组件
        vue_app:component(name, setting);
        component = vue_app:component(name)
    else
        -- Vue3版本
        component = Vue:component(name, setting)
    end

    -- 释放内存
    component_template[name] = nil

    -- 返回vue组件
    return component
end

local function removeComments(str)

    local delimiter = '\r'

    local pos, arr, line, res = 0, {}, '', ''

    -- 遍历分割字符串
    for st, sp in function() return string.find(str, delimiter, pos, true) end do

        -- 获取一行字符串内容
        line = string.sub(str, pos, st - 1)
        -- 将行中注释符号后面的内容删除
        line = string.gsub(line,'%-%-.+','')
        -- 保存字符串
        res = res .. line .. '\r' 

        pos = sp + 1
    end

    return res
end

local function parseImport(script)

    local list = {}
    local clone = script .. '\n'

    -- 删除单行注释
    script = removeComments(script)

    if not script or script == '' then
        return script, list
    end

    for url in string.gmatch(script,'loader%.import%((.-)%)') do
        -- 删掉前后的引号
        local absolute_url = parseAbsoluteUrl(string.gsub(url, '[\'\"]', ''))
        -- 保存到list表中
        table.insert(list, absolute_url)
        -- 替换脚本中原来的导入代码
        clone, count = string.gsub(clone, 'loader%.import%(('..url..')%)', 'registered_component["' .. absolute_url .. '"]')
    end

    return clone, list
end

local function checkParentImportProgress(url)

    -- 判断url所属父组件的情况
    local relation = components_relation

    for parent, parent_list in pairs(relation) do

        -- 用以标记当前父组件是否需要再遍历检查一次
        local reloop = false

        for child, status in pairs(parent_list) do

            if child == url then
                reloop = true
                parent_list[url] = true
                components_relation[parent][url] = true
            end
        end

        if reloop == true then

            local is_finish = true

            for child, status in pairs(parent_list) do
                if not status then
                    is_finish = false
                end
            end

            if is_finish then
                -- 改组件的所有子组件均已加载完成
                local finish_callback = components_callback[parent]
                -- 调用组件的回调函数
                if finish_callback then
                    finish_callback()
                end

                -- 从关系表和回调表中移除该组件
                components_relation[parent] = nil
                components_callback[parent] = nil
            end
        end
    end
end

local function requestLua(url, callback)

    if requesting_component[url] then
        return true
    end

    -- 设置加载状态
    requesting_component[url] = true

    -- 通过异步请求获取lua文件内容
    asyncXhrGet(url,function(content)

        -- 分析脚本中是否包含"loader.import"，有则替换掉
        local script, list = parseImport(content)

        -- 加载组件后，再执行脚本
        importComponents(url, list, function()

            -- 获取脚本成功后，执行脚本
            local fn, err = load(script, "stdin")

            if not fn then
                error(url .. '加载出错: ' .. err)
                return false
            end

            -- 执行lua文件
            registered_component[url] = fn()

            -- 检测所属父组件的全部子组件是否已加载完成
            checkParentImportProgress(url)

            -- 如果有提供回调函数，则将注册好的组件传递给回调函数
            if callback then
                callback(registered_component[url])
            end

        end)

    end)

end

local function requestComponent(url, callback)

    if requesting_component[url] then
        return true
    end

    -- 设置加载状态
    requesting_component[url] = true

    -- 通过异步请求获取vue模版内容
    asyncXhrGet(url, function(content)

        -- 分析vue模版内容
        local template, script, style = parseVueSFC(content)

        -- 分析script中的所需加载的其他组件
        local script, list = parseImport(script)

        -- 加载组件
        importComponents(url, list, function()

            -- 注册并保存vue组件
            registered_component[url] = registerVueSFC(url, template, script, style)

            -- 检测所属父组件的全部子组件是否已加载完成
            checkParentImportProgress(url)
            -- 如果有提供回调函数，则将注册好的组件传递给回调函数
            if callback then
                callback(registered_component[url])
            end
        end)

    end)
end

function importComponents(parent, list, callback)

    if not components_relation[parent] then
        components_relation[parent] = {}
        components_callback[parent] = callback
    end

    if #list <= 0 then
        -- 没有下级组件，直接认为加载完成，并调用回调函数
        return callback()
    end

    local is_finish = true
    local child_url = ''

    -- 遍历list
    for _, url in pairs(list) do

        -- 判断该组件是否已加载
        if registered_component[url] then

            -- 修改组件关系状态
            components_relation[parent][url] = true

            -- 记录组件地址，以便下面使用
            child_url = url
        else
            is_finish = false
            -- 修改组件关系状态
            components_relation[parent][url] = false

            -- 判断该组件是否正在加载
            if not requesting_component[url] then

                -- 判断是否vue组件
                if string.find(url,[[.vue]]) then
                    -- 尝试加载该组件
                    requestComponent(url)
                else
                    requestLua(url)
                end

            end
        end
    end

    -- 当所有子组件均已加载，检测父组件进度
    if is_finish == true then
        checkParentImportProgress(child_url)
    end

end

local function array(t)
    local a = js.new(js.global.Array)
    for k, v in ipairs(t) do
        a:push(v)
    end
    return a
end

local function object(t)
    local o = js.new(js.global.Object)
    for k, v in pairs(t) do
        assert(type(k) == "string" or js.typeof(k) == "symbol", "JavaScript only has string and symbol keys")
        o[k] = v
    end
    return o
end

local function asyncImport(url, callback)

    url = parseAbsoluteUrl(url)

    -- 判断是否vue组件
    if string.find(url,[[.vue]]) then
        -- 尝试加载该组件
        requestComponent(url, callback)
    else
        requestLua(url, callback)
    end
end

local function bootstrap(options)

    request_version = options.version or ''

    local entry = parseAbsoluteUrl(options.entry)

    asyncXhrGet(entry,function(content)

        -- 分析脚本中是否包含"loader.Import"，有则替换掉
        local script, list = parseImport(content)

        -- 加载组件后，再执行脚本
        importComponents(entry, list, function()

            -- 获取脚本成功后，执行脚本
            local fn, err = load(script, "stdin")

            if not fn then
                error(entry .. '加载出错: ' .. err)
                return false
            end

            -- 执行入口文件
            local status = fn()
        end)

    end)
end

-- 放到全局变量loader中
loader = {
    require = asyncImport,
    arr = array,
    obj = object,
    export = object,
    bootstrap = bootstrap,
}

obj = object
arr = array

-- 判断是否需要自动加载
local function auto_bootstrap()

    local el = document:getElementById('vue-lua-web-loader')

    if el == js.null then
        return false
    end

    local entry = el:getAttribute('entry')

    if entry == js.null then
        return false
    end

    local version = el:getAttribute('version')
    -- 没有指定版本或版本为auto时，采取当前系统时间为版本号
    if version == js.null or version == 'auto' then
        version = global.Date.now()
    end

    -- 加载入口页面
    bootstrap({
        version = version,
        entry = entry
    })
end

auto_bootstrap()
